🆕 新增/修改的程式碼
export function normalizeLang(code) {
if (!code) return "";
const c = code.trim();
if (LANG_MAP[c]) return c;
// 寬鬆別名
const lower = c.toLowerCase();
if (lower === "zh" || lower === "zh_tw" || lower === "zh-hant") return "zh-TW";
if (lower === "zh_cn" || lower === "zh-hans") return "zh-CN";
return LANG_MAP[c] ? c : "";
}
export function assertSupported(target) {
if (!LANG_MAP[target]) {
const list = Object.keys(LANG_MAP).join(", ");
throw new Error(不支援的語言代碼:${target},請使用其中之一:${list}
);
}
}
/**
- ${g.source} => ${g.target}
);/**
區段、不翻譯其中內容 */ function buildFormatProtectionHint(preserveFormat = true) { if (!preserveFormat) return ""; return [ "如原文含 Markdown/程式碼區塊(
或 inline
),",/**
if (!text?.trim()) throw new Error("text 為必填。");
const tgt = normalizeLang(targetLang) || targetLang;
assertSupported(tgt);
const sys = [你是專業的翻譯員,將輸入文字翻譯為 ${LANG_MAP[tgt]}。
,語氣:${tone};除非為必要語法調整,請忠實於原文。
,
buildFormatProtectionHint(preserveFormat),
buildGlossaryHint(glossary),
"若輸入已是目標語言,請僅進行用詞標準化與小幅潤飾,不可改變技術含義。",
].filter(Boolean).join("\n");
const user = [
sourceLang ? 來源語言:${LANG_MAP[normalizeLang(sourceLang)] || sourceLang}
: "來源語言:自動判斷",
"請只回覆純文字譯文,不要加註任何說明。",
"==== 原文開始 ====",
text,
"==== 原文結束 ====",
].join("\n");
const res = await openai.chat.completions.create({
model: "gpt-4o-mini",
temperature: 0.2,
messages: [
{ role: "system", content: sys },
{ role: "user", content: user }
],
});
const target = res.choices?.[0]?.message?.content?.trim() || "";
return { source: text, target, detectedSourceLang: sourceLang ? undefined : "(model-detected)" };
}
/**
/**
const glos = buildGlossaryHint(glossary);
const sys = [
"你是專業的譯後品質評估員,請針對給定的原文與譯文評分與提出具體建議。",
"請以純 JSON 回覆,格式:",
"{"scores":{"adequacy":0.0,"fluency":0.0,"terminology":0.0},"suggestions":["..."]}",
"分數 0~1,小數到兩位。adequacy=忠實度;fluency=流暢度;terminology=術語一致性。",
glos
].filter(Boolean).join("\n");
const user = [
"=== 原文 ===",
source,
"=== 譯文 ===",
target,
].join("\n");
const res = await openai.chat.completions.create({
model: "gpt-4o-mini",
temperature: 0.1,
messages: [
{ role: "system", content: sys },
{ role: "user", content: user }
],
});
const raw = res.choices?.[0]?.message?.content?.trim() || "{}";
const json = raw.match(/(?:json)?\s*([\s\S]*?)
/i)?.[1] ?? raw;
const obj = JSON.parse(json);
return obj;
}
const args = Object.fromEntries(
process.argv.slice(2).reduce((acc, cur, i, arr) => {
if (cur.startsWith("--")) {
const key = cur.replace(/^--/, "");
const val = arr[i + 1] && !arr[i + 1].startsWith("--") ? arr[i + 1] : true;
acc.push([key, val]);
}
return acc;
}, [])
);
async function main() {
const task = args.task || "chat";
if (task === "translate") {
const mode = args.mode || "one"; // one | batch | qe
const targetLang = args.to || "zh-TW";
const sourceLang = args.from || ""; // 可留空自動判斷
// 解析 glossary:"A:B,C:D"
const glossary = (args.glossary || "")
.split(",")
.map(p => p.trim())
.filter(Boolean)
.map(pair => {
const [source, target] = pair.split(":").map(s => s?.trim()).filter(Boolean);
return source && target ? { source, target } : null;
})
.filter(Boolean);
if (mode === "one") {
const text = args.text || "RAG enables retrieval over private knowledge bases.";
const out = await translateOne({
text,
sourceLang,
targetLang,
glossary,
preserveFormat: args.keepfmt !== "false",
tone: args.tone || "neutral",
});
console.log("\n=== 單筆翻譯 ===\n");
console.log(out.target);
} else if (mode === "batch") {
// 批次:用分號 ;; 分隔
const raw = args.texts || "Hello;;Good morning;;This is a test.";
const items = raw.split(";;").map(s => s.trim()).filter(Boolean);
const out = await translateBatch({
items,
sourceLang,
targetLang,
glossary,
preserveFormat: args.keepfmt !== "false",
tone: args.tone || "neutral",
});
console.log("\n=== 批次翻譯(JSON) ===\n");
console.log(JSON.stringify(out, null, 2));
} else if (mode === "qe") {
const src = args.src || "RAG enables retrieval over private knowledge bases.";
const tgt = args.tgt || "RAG 讓你可以在私有知識庫上進行檢索。";
const out = await qualityCheck(src, tgt, glossary);
console.log("\n=== 品質檢查(QE) ===\n");
console.log(JSON.stringify(out, null, 2));
} else {
console.log("未知模式,請使用 --mode one | batch | qe");
}
} else {
// ...你原本的其他 task 分支(chat, image, vision, stt, tts, mm, docsum 等)
}
}
main().catch((e) => {
console.error("發生錯誤:", e.message);
process.exit(1);
});
▶️ CLI 使用範例
npm run day12:one --silent
npm run day12:batch --silent
npm run day12:qe --silent
node index.js --task translate --mode one --to en --text "請看這段程式碼:const x=1;
不要翻譯裡面的內容。